const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const path = require('path');
const {v4: uuid} = require('uuid')
const nodemailer = require('nodemailer');
const HttpError = require('../models/errorModel');
const User = require('../models/userModel')

const registerUser = async (req, res, next) => {
    try {
        const { name, email, password, confirmPassword } = req.body;
    //请求参数中的昵称、邮箱、密码、再次确定密码
        if (!name || !email || !password || !confirmPassword) {
            //如果没有昵称、邮箱、密码、再次确定密码参数
            return next(new HttpError('未填写必填项.', 422));
        }//则返回错误为未填写必填项
        const newEmail = email.toLowerCase();
        //创建一个新的邮箱变量，让邮箱大写转小写
        const emailExists = await User.findOne({ email: newEmail });
        //根据新的邮箱变量，检查是否存在指定的邮箱，
        if (emailExists) {
          // 如果已存在
            return next(new HttpError('电子邮件已经存在，请直接登录.', 422));
        }//则返回错误电子邮件已经存在，请直接登录
        if (password.length < 6) {
            // 如果输入密码长度少于6个字符，
            return next(new HttpError('密码至少为6个字符，请重新输入.', 422));
        }//则返回错误为密码至少为6个字符，请重新输入
        if (password !== confirmPassword) {
            //如果输入密码不符合确认密码
            return next(new HttpError('两次密码不匹配，请重新输入.', 422));
        }//则返回错误两次密码不匹配，请重新输入
        const salt = await bcrypt.genSalt(10);
        //使用 bcrypt 库生成盐值：
        const hashedPassword = await bcrypt.hash(password, salt);
        //使用 bcrypt 对密码进行加密，并将盐值作为参数传入：
        const newUser = await User.create({ name, email: newEmail, password: hashedPassword });
       //创建一个新的用户对象，并将其保存到数据库中：
        const { _id: id } = newUser;
        //从当前注册的user中取出id属性
        newUser.messages.push({text:"您好！账号已注册成功，请保管好您的密码", created: Date.now()});
        // 发送消息到用户的 messages 提示您好！账号已注册成功，请保管好您的密码
        await newUser.save();
        //等待新的用户保存
        const token = jwt.sign({id, name}, process.env.JWT_SECRET, {expiresIn: '1d'})
        //生成一个token数据，用id和name 进行jwt签名得到token 返回给前端，保留数据为1天
        res.status(201).json({id, token, name, email: newEmail})
        //将id token name email等数据返回给前端
    } catch (error) {
        //如果发生异常
        return next(new HttpError('用户创建失败，请重新检查注册.', 422));
    }//则返回错误响应为用户创建失败，请重新检查注册
};

const loginUser = async (req, res, next) => {
    try {
        const {email, password} = req.body;
        //请求邮箱、密码的参数中
        if (!email || !password) {
            //如果传数据邮箱或密码为空
            return next(new HttpError(`请填写必填项.`, 422));
        }//则返回错误提醒请填写必填项
        const newEmail = email.toLowerCase();
       //根据输入的邮箱变量，让邮箱大写转小写
        const user = await User.findOne({email: newEmail});
        //根据邮箱变量，检查是否存在指定的邮箱，
        if (!user) {
            //如果用户不存在
            return next(new HttpError(`不存在该用户信息，请前往注册.`, 422));
        }//则返回错误提示为不存在该用户信息，请前往注册
        const comparePassword = await bcrypt.compare(password, user.password)
        //使用bcrypt对输入的密码和数据库的密码进行对比
        if (!comparePassword) {
            return next(new HttpError(`用户名或密码不正确，请重新输入.`, 422));
        }
        //如果输入密码不确定，则返回用户名或密码不正确，请重新输入
        const {_id: id, name } = user;
        //从当前的user中取出id和name属性
        const token = jwt.sign({id, name}, process.env.JWT_SECRET, {expiresIn: '1d'})
        //生成一个token数据，用id和name 进行jwt签名得到token 返回给前端，保留数据为1天
        res.status(200).json({token, id, name})
        //将id token name 等数据返回给前端
    } catch (error) {
        //如果发生异常
        return next(new HttpError(`登录失败，请检查您的信息是否正确.`, 422));
    }//则返回错误响应为登录失败，请检查您的信息是否正确
}

// 忘记密码
const forget = async (req, res, next) => {
    try {
        const {email} = req.body;
        // 从请求参数获取邮箱
        if (!email) {
            return next(new HttpError(`请填写电子邮箱号.`, 422));
        }//如果邮箱数据为空，则返回提示请填写电子邮箱号
        const newEmail = email.toLowerCase();
        // 根据输入的邮箱数据，让邮箱大写转小写
        const user = await User.findOne({email: newEmail});
       //根据邮箱变量，检查是否存在指定的邮箱，
        if (!user) {
            //如果用户不存在
            return next(new HttpError(`不存在该用户信息，请前往注册.`, 422));
        }//则返回错误提示为不存在该用户信息，请前往注册
        const {_id: id} = user;
        // 从当前的user中取出id和name属性
        const token = jwt.sign({id, newEmail}, process.env.JWT_SECRET, {expiresIn: '1d'})
        //生成一个token数据，用id和用户账号 进行jwt签名得到token 返回给前端，保留数据为1天
        const transporter = nodemailer.createTransport({
            host: 'smtp.163.com',
            port: 465,
            secure: true, // 使用SSL
            auth: {
                user: process.env.MAIL_SENDER, // 你的163邮箱
                pass: process.env.MAIL_CODE // 你的邮箱密码（不是登录密码，是授权码）
            }
        });//构造发送邮件的参数，并设置以下属性： 主体为smtp.163.com，端口为465，安全控制器为使用ssl加密，授权您的env邮箱和密码

        transporter.sendMail({
            // 发送邮件数据
            from: process.env.MAIL_SENDER, // 发送者邮箱
            to: newEmail, // 接收者邮箱
            subject: '找回密码', // 邮件标题
            html: '<b>根据此链接重设密码: </b><a href="http://localhost:3000/reset/'+token+'"> 复制链接到浏览器打开 http://localhost:3000/reset/'+token+' </a> (注意：24小时内有效)'
        }, //发送邮箱内容
            (error, info) => {
            if (error) {
                return console.log(error);
            }//如果发生异常，则返回控制台日志提示
        });
        res.status(200).json({id})
        //将该操作的数据返回给前端
    } catch (error) {
        //如果发生异常
        return next(new HttpError(`请求失败，请检查网络是否正常.`, 422));
    }//则返回错误提示求失败，请检查网络是否正常
}

const change = async (req, res, next) => {
    try {
        const { password, confirmPassword } = req.body;
     //请求密码和确定密码的参数
        if (!password || !confirmPassword) {
            //如果传回的密码和确定密码为空
            return next(new HttpError('请填写新密码和重复密码.', 422));
        }//则返回错误提示为请填写新密码和重复密码
        const user = await User.findById(req.user.id)
        //根据用户ID查询，检查是否存在指定用户
        if (!user) {
            //如果用户不存在
            return next(new HttpError(`未找到用户.`, 404));
        }//则返回错误提示未找到用户

        if (password.length < 6) {
            return next(new HttpError('密码至少为6个字符，请重新输入.', 422));
        }//如果输入密码长度少于6个字符，则返回错误提示为密码至少为6个字符，请重新输入
        if (password !== confirmPassword) {
            return next(new HttpError('两次密码不匹配，请重新输入.', 422));
        }//如果输入密码不符合确认密码，则两次密码不匹配，请重新输入
        const salt = await bcrypt.genSalt(10);
        //使用 bcrypt 库生成盐值：
        const hashedPassword = await bcrypt.hash(password, salt);
        //使用 bcrypt 对密码进行加密，并将盐值作为参数传入：
        const newInfo = await User.findByIdAndUpdate(req.user.id, {password: hashedPassword}, {new: true})
        //根据用户ID更新密码并返回更新后的用户信息
        const { _id: id, name } = newInfo;
        //从当前账户信息中取出id和name属性
        const token = jwt.sign({id, name}, process.env.JWT_SECRET, {expiresIn: '1d'})
      //生成一个token数据，用id和name 进行jwt签名得到token 返回给前端，保留数据为1天
        res.status(201).json({id, token, name})
       //将更新后的id token和name的数据返回给前端
    } catch (error) {
        //如果发生异常
        return next(new HttpError('修改密码失败，请重新操作.', 422));
    }//则返回错误提示为修改密码失败，请重新操作
};


const getUser = async (req, res, next) => {
   try {
    const {id} = req.params;
    //请求ID参数中
    const user = await User.findById(id).select('-password');
   //根据user表查询指定id数据信息，但不包含密码属性
    if (!user) {
        //如果查看用户不存在
        return next(new HttpError(`未找到用户.`, 404));
    }//则返回错误未找到用户
    if (process.env.ADMIN_MAIL === user.email) {
        //如果用户邮箱符合端口.env的管理员邮箱
        let copy = {...user._doc, admin: true};
        //复制user的全部信息 额外补充admin属性为true
        res.status(200).json(copy)
        //返回copy数据给前端
    } else {
        res.status(200).json(user)
    }//返回用户信息给前端
   } catch (error) {
       //如果发生异常
    return next(new HttpError(error));
   }//则返回一个表示服务器内部错误的响应
}

const changeAvatar = async (req, res, next) => {
    try {
        if (!req.files.avatar) {
            //如果没有给头像这个属性传文件
            return next(new HttpError(`请上传选择图片.`, 422));
        }//则返回错误提示请上传选择图片
        const user = await User.findById(req.user.id)
        //根据user表查询指定用户id数据信息
        if (user.avatar) {
            //如果有用户头像
            fs.unlink(path.join(__dirname, '..', 'uploads', user.avatar), (err) => {
               //就把原始头像删除，加入重新上传的头像
                if (err) {
                    //如果发生异常
                    return new HttpError(err);
                }//则返回表示服务器内部错误的响应
            })
        }

        const {avatar} = req.files;
        //请求文件中的头像
        console.log(avatar.size)
        // 检查数据库里的文件大小
        if (avatar.size > 5000000) {
            //如果头像大小大于5MB
            return next(new HttpError(`图像大小不能超过5MB.`, 422));
        }//则返回错误提示图像大小不能超过5MB

        let fileName;
        //定义一个文件名变量
        fileName = avatar.name;
        //让文件名等于上传的文件的名字
        let splittedFileName = fileName.split('.')
        //然后将文件名根据小数点分割
        let newFileName = splittedFileName[0] + uuid() + '.' + splittedFileName[splittedFileName.length - 1];
        //再定义一个新的文件名 让它等于等于分割文件名的第一段 加上一个uuid 再加上小数点 最后加上分割文件名的第二段
        avatar.mv(path.join(__dirname, '..', 'uploads', newFileName), async (err) => {
            //将文件移动到upload文件夹里并用新的文件名。
            if (err) {
                return new HttpError(err);
            }//如果发生异常，则返回表示服务器内部错误的响应

            const updatedAvatar = await User.findByIdAndUpdate(req.user.id, {avatar: newFileName}, {new: true})
         //根据用户ID更新头像并返回更新了头像信息的用户数据
            if (!updatedAvatar) {
                //如果没有更新或异常
                return new HttpError(`更新用户头像失败.`);
            }//则返回错误提示为更新用户头像失败

            res.status(200).json(updatedAvatar);
        })//将更新后的头像返回给前端
    } catch (error) {
        return next(new HttpError(error));
    }//如果发生异常，则返回表示服务器内部错误的响应
}

const editUser = async (req, res, next) => {
    try {
        const {name, currentPassword, newPassword, confirmNewPassword} = req.body;
   //请求名字、当前密码、新密码、确定新密码的参数中
        if (!name && (!currentPassword || !newPassword)) {
            //如果没有传回这些参数
            return next(new HttpError(`Fill in all fields.`, 422));
        }//则返回错误提示为请填写必填项
        const user = await User.findById(req.user.id)
        // 从user数据库中获取指定ID数据
        if (!user) {
            //如果没有用户数据
            return next(new HttpError(`未找到用户.`, 404));
        }//则返回错误提示为未找到用户。
        if(currentPassword && newPassword) {
            //如果传回了密码参数
            const validateUserPassword = await bcrypt.compare(currentPassword, user.password)
            // 使用bcrypt比对当前密码和数据库密码
            if (!validateUserPassword) {
                //如果验证用户密码不同
                return next(new HttpError(`当前输入密码不正确，请重新输入.`, 422));
            }//则返回提示当前输入密码不正确，请重新输入
            if (newPassword !== confirmNewPassword) {
                //如果传回新密码和确定密码不相等
                return next(new HttpError(`两次密码不匹配，请重新输入.`, 422));
            }//则返回错误提示两次密码不匹配，请重新输入
            const salt = await bcrypt.genSalt(10)
            //使用 bcrypt 库生成盐值：
            const hash = await bcrypt.hash(newPassword, salt)
            //使用 bcrypt 对密码进行加密，并将盐值作为参数传入
            const newInfo = await User.findByIdAndUpdate(req.user.id, {name, password: hash}, {new: true})
            //根据用户ID更新密码并返回更新后的用户信息
            res.status(200).json(newInfo)
            //将用户更新信息返回给前端
        } else {
            const newInfo = await User.findByIdAndUpdate(req.user.id, {name}, {new: true})
            //根据用户ID更新名字并返回更新后的用户信息
            res.status(200).json(newInfo)
        }//将用户更新信息返回给前端
        
    } catch (error) {
        return new HttpError(err);
    }//如果发生异常，则返回表示服务器内部错误的响应
}


const editInfo = async (req, res, next) => {
    try {
        const {technique, experience} = req.body;
    //请求技能和经验参数中
        if (!technique && !experience) {
            //如果传回信息为空
            return next(new HttpError(`填写所有字段.`, 422));
        }//则返回错误为填写所有字段

        // get user from database
        const user = await User.findById(req.user.id)
        //从user数据库获取指定用户ID
        if (!user) {
            //如果查询没有该用户
            return next(new HttpError(`未找到用户.`, 404));
        }//则返回错误提示为未找到用户
        const newInfo = await User.findByIdAndUpdate(req.user.id, {technique, experience}, {new: true})
        //根据用户ID更新技术信息和项目经验信息并返回更新后的用户信息
        res.status(200).json(newInfo)
        //将更新后用户信息返回给前端
    } catch (error) {
        return new HttpError(err);
    }//如果发生异常，则返回表示服务器内部错误的响应
}

module.exports = { registerUser, loginUser, getUser, forget, change, changeAvatar, editUser, editInfo }